/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.acworks.acroute.v2.impl;

import com.acworks.acroute.v2.Arc;
import com.acworks.acroute.v2.ArcDirectionType;
import com.acworks.acroute.v2.Graph;
import com.acworks.acroute.v2.Node;
import com.acworks.acroute.v2.NodeType;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;

/**
 *
 * @author nikita
 */
abstract public class AbstractGraph implements Graph, ArcChangeListener {

    final private SortedSet<Node> nodes;
    private Node startingNode;
    private Node endingNode;
    private Set<Arc> arcs;
    final private Map<ArcCacheKey, Set<Arc>> incidentArcCache = new HashMap<ArcCacheKey, Set<Arc>>();

    AbstractGraph(SortedSet<Node> graphNodes) {
        this(graphNodes, null);
    }

    public AbstractGraph(SortedSet<Node> graphNodes, Set<Arc> arcs) {
        assert graphNodes != null;
        this.nodes = graphNodes;

        for (Node n : graphNodes) {
            if (n.getType() == NodeType.STARTING) {
                if (startingNode != null) {
                    throw new IllegalArgumentException("cannot construct a graph with multiple Starting Nodes");
                }
                startingNode = n;
            }

            if (n.getType() == NodeType.ENDING) {
                if (endingNode != null) {
                    throw new IllegalArgumentException("cannot construct a graph with multiple Ending Nodes");
                }
                endingNode = n;
            }
        }

        setArcs(arcs);
    }

    @Override
    public Node getStartingNode() {
        return startingNode;
    }

    @Override
    public SortedSet<Node> getNodes() {
        return nodes;
    }

    @Override
    public Set<Arc> getArcs() {
        return arcs;
    }

    @Override
    public Set<Arc> getIncidentArcs(Node node, ArcDirectionType type) {
        assert node != null;

        ArcCacheKey key = new ArcCacheKey(node, type);

        Set<Arc> incidentArcs = incidentArcCache.get(key);

        if (incidentArcs == null) {
            incidentArcs = doGetIncidentArcs(node, type);

//            for (Arc arc : incidentArcs) {
//                arc.getArcChangeListeners().add(this);
//            }
            incidentArcCache.put(key, incidentArcs);
        }

        //always return a copy so that subsequent modifications do not modify cache
        return new HashSet<Arc>(incidentArcs);
    }

    private Set<Arc> doGetIncidentArcs(Node node, ArcDirectionType type) {
        Set<Arc> incidentArcs = new HashSet<Arc>(4);
        for (Arc arc : getArcs()) {
            if (arc.isIncident(node)) {
                if (type != null && type != arc.getType()) {
                    continue;
                } else {
                    incidentArcs.add(arc);
                }
            }
        }
        return incidentArcs;
    }

    @Override
    public Set<Arc> getIncidentDirectedArcs(Node node, boolean originating) {
        Set<Arc> incidentDirectedArcs = getIncidentArcs(node, ArcDirectionType.DIRECTED);
        Iterator<Arc> iter = incidentDirectedArcs.iterator();
        while (iter.hasNext()) {
            Arc arc = iter.next();

            if (originating && !arc.getSourceNode().equals(node)) {
                iter.remove();
            } else if (!originating && !arc.getTargetNode().equals(node)) {
                iter.remove();
            }
        }
        return incidentDirectedArcs;
    }

    @Override
    public Node getEndingNode() {
        return endingNode;
    }

    /**
     * @param arcs the arcs to set
     */
    protected void setArcs(Set<Arc> arcs) {
        this.arcs = arcs;
    }

    /**
     * delete all cache entries containing given arcs
     * @param arcs
     */
    void invalidateCacheEntries(Set<Arc> arcs) {
        assert arcs != null;

        Set<Node> affectedNodes = new HashSet<Node>();
        for (Arc arc : arcs) {
            affectedNodes.add(arc.getSourceNode());
            affectedNodes.add(arc.getTargetNode());
        }

        //delete all entries where Node in key is either a sourceNode or targetNode

        Set<Entry<ArcCacheKey, Set<Arc>>> entrySet = incidentArcCache.entrySet();
        Iterator<Entry<ArcCacheKey, Set<Arc>>> iter = entrySet.iterator();
        while (iter.hasNext()) {
            Map.Entry<ArcCacheKey, Set<Arc>> entry = iter.next();
            if (!Collections.disjoint(arcs, entry.getValue())) {
                iter.remove();
            } else if (affectedNodes.contains(entry.getKey().getNode())) {
                iter.remove();
            }
        }
    }

    @Override
    public void directionTypeUpdated(Arc arc, ArcDirectionType oldType, ArcDirectionType newType) {
    }
}
